package space.misiro.ledgers.keycloak.client.impl

import KeycloakClient
import jakarta.ws.rs.core.Response
import org.keycloak.admin.client.CreatedResponseUtil
import org.keycloak.admin.client.Keycloak
import org.keycloak.admin.client.resource.ClientsResource
import org.keycloak.models.UserModel.RequiredAction
import org.keycloak.representations.idm.CredentialRepresentation
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import space.misiro.ledgers.keycloak.client.api.KeycloakDataService
import space.misiro.ledgers.keycloak.client.config.KeycloakClientConfig
import space.misiro.ledgers.keycloak.client.mapper.KeycloakDataMapper
import space.misiro.ledgers.keycloak.client.model.KeycloakRealm
import space.misiro.ledgers.keycloak.client.model.KeycloakUser
import space.misiro.ledgers.keycloak.client.rest.KeycloakTokenRestClient
import java.net.InetAddress

/**
 * Keycloak数据服务实现类
 *
 * 核心职责：
 * 1. 提供Keycloak基础设施的自动化配置管理
 * 2. 实现用户全生命周期管理（创建/更新/删除/密码管理）
 * 3. 管理领域级安全配置（角色/客户端/作用域）
 * 4. 与Keycloak Admin API的深度集成
 *
 * 功能特性：
 * - 基于Spring Boot的配置注入（通过KeycloakClientConfig）
 * - 使用Keycloak Admin Client实现领域架构自动化部署
 * - 支持用户密码的多种重置方式（直接设置/邮件验证）
 * - 完整的角色管理体系（分配/撤销/领域角色管理）
 * - 客户端应用的自动化注册与配置
 * - 完善的错误处理与日志追踪机制
 *
 * 技术组件：
 * - 集成Keycloak Admin REST API
 * - 采用Jakarta WS-RS规范进行服务调用
 * - 通过SLF4J记录操作日志
 * - 支持动态主机地址检测（通过InetAddress）
 * - 提供可配置的Token生命周期管理
 */
@Service
class KeycloakDataServiceImpl(
    private val keycloak: Keycloak,
    private val mapper: KeycloakDataMapper,
    private val configuration: KeycloakClientConfig,
    private val keycloakTokenRestClient: KeycloakTokenRestClient
) : KeycloakDataService {

    private val log: Logger = LoggerFactory.getLogger(KeycloakDataServiceImpl::class.java)

    @Value("\${local.server.port:8088}")
    private var port: Int = 8088

    @Value("\${ledgers.token.lifetime.seconds.login:600}")
    private var loginTokenTTL: Int = 600

    @Value("\${ledgers.token.lifetime.seconds.full:600}")
    private var fullTokenTTL: Int = 600

    /**
     * 创建默认的Keycloak领域架构
     *
     * 流程说明：
     * 1. 创建基础领域(Realm)
     * 2. 添加领域级客户端作用域(Client Scopes)
     * 3. 创建领域级角色(Realm Roles)
     * 4. 创建客户端应用并配置作用域
     *
     * @return 创建完成的Keycloak领域对象
     */
    override fun createDefaultSchema() = KeycloakRealm(
        configuration.clientRealm, loginTokenTTL, fullTokenTTL,
        configuration.smtpServer
    ).run {
        createRealm(this@run)
        createRealmScopes(this@run)
        createRealmRoles(this@run)
        createClient(realm)
    }

    /**
     * 检查指定客户端是否存在
     *
     * @return 如果客户端存在返回true，否则返回false
     */
    override fun clientExists() = runCatching {
        keycloak.realm(configuration.clientRealm).clients().findByClientId(configuration.externalClientId).isNotEmpty()
    }.getOrElse { false }

    /**
     * 根据登录名获取用户信息
     *
     * @param realm Keycloak领域名称（根据接口继承要求保留参数，实际使用配置中的固定领域）
     * @param login 用户登录名
     * @return 匹配的用户对象，未找到时返回null
     */
    override fun getUser(realm: String, login: String) = getUserByIdentifier(login)

    /**
     * 创建新用户并分配角色
     *
     * @param user 包含用户信息的KeycloakUser对象
     * @return 创建成功后返回用户ID，若失败则无返回值
     */
    override fun createUser(user: KeycloakUser) =
        keycloak.realm(configuration.clientRealm).users().create(mapper.createUserRepresentation(user)).use {
            it.takeIf { HttpStatus.CREATED.value() == it.status }
        }?.let { CreatedResponseUtil.getCreatedId(it) }?.let {
            log.info("User [${user.login}] is created with id: $it")
            assignUserRoles(configuration.clientRealm, it, user.realmRoles)
        } ?: Unit

    /**
     * 更新用户信息和角色配置
     *
     * @param user 包含更新信息的KeycloakUser对象
     * @param userIdentifier 用户唯一标识（可以是ID或登录名）
     */
    override fun updateUser(user: KeycloakUser, userIdentifier: String) =
        getUserResourceByIdentifier(userIdentifier)?.run {
            update(mapper.toUpdateUserPresentation(toRepresentation(), user))
            assignUserRoles(configuration.clientRealm, toRepresentation().id, user.realmRoles)
        }?.also { log.debug("User [${user.login}] was updated in keycloak.") } ?: log.info(
            USER_NOT_FOUND_IN_KEYCLOAK,
            user.login
        )

    /**
     * 删除指定用户
     *
     * @param login 要删除的用户登录名
     */
    override fun deleteUser(login: String) = getUserByIdentifier(login)?.run {
        keycloak.realm(configuration.clientRealm).users().delete(id)
        return
    }?.also { log.info("User [$login] was deleted from keycloak.") } ?: log.info(USER_NOT_FOUND_IN_KEYCLOAK, login)

    /**
     * 检查用户是否存在
     *
     * @param login 要检查的用户登录名
     * @return 用户存在返回true，否则返回false
     */
    override fun userExists(login: String) = getUserByIdentifier(login) != null

    /**
     * 重置用户密码（直接设置新密码）
     *
     * @param login 要重置密码的用户登录名
     * @param password 新密码明文
     */
    override fun resetPassword(login: String, password: String) = getUserResourceByIdentifier(login)?.resetPassword(
        CredentialRepresentation().apply {
            isTemporary = false
            type = CredentialRepresentation.PASSWORD
            value = password
        })?.also { log.info("User $login was password reset.") } ?: log.info(USER_NOT_FOUND_IN_KEYCLOAK, login)

    /**
     * 通过邮件发送密码重置链接
     *
     * @param login 要重置密码的用户登录名
     */
    override fun resetPasswordViaEmail(login: String) = getUser(
        configuration.clientRealm,
        login
    )?.run {
        keycloakTokenRestClient.executeActionsEmail(
            "Bearer ${keycloak.tokenManager().accessToken.token}",
            id,
            listOf(RequiredAction.UPDATE_PASSWORD.name)
        )
        return
    }?.also { log.info("User [$login], email for updating password was sent.") } ?: log.info(
        USER_NOT_FOUND_IN_KEYCLOAK,
        login
    )

    /**
     * 为用户分配领域角色
     *
     * @param login 用户登录名
     * @param realmRoles 要分配的角色名称列表
     */
    override fun assignRealmRoleToUser(login: String, realmRoles: List<String>) = getUserByIdentifier(login)?.run {
        assignUserRoles(configuration.clientRealm, id, realmRoles)
    }
        ?.also { log.info("Realm roles $realmRoles were assigned to User[realm: ${configuration.clientRealm}, login: $login]") }
        ?: log.info(USER_NOT_FOUND_IN_KEYCLOAK, login)

    /**
     * 移除用户的领域角色
     *
     * @param login 用户登录名
     * @param realmRoles 要移除的角色名称列表
     */
    override fun removeRealmRoleFromUser(login: String, realmRoles: List<String>) =
        getUserResourceByIdentifier(login)?.roles()?.realmLevel()
            ?.remove(realmRoles.map { getRealmRole(configuration.clientRealm, it) })
            ?.also { log.info("Realm roles $realmRoles were remove from User[realm: ${configuration.clientRealm}, login: $login]") }
            ?: log.info(USER_NOT_FOUND_IN_KEYCLOAK, login)

    /**
     * 根据用户标识获取用户资源对象
     *
     * @param login 用户登录名或ID
     * @return 对应的用户资源对象，未找到时返回null
     */
    private fun getUserResourceByIdentifier(login: String) =
        getUserByIdentifier(login)?.id?.run { keycloak.realm(configuration.clientRealm).users().get(this) }

    /**
     * 为用户分配多个领域角色
     *
     * @param realm 目标领域名称
     * @param userId 用户ID
     * @param realmRoles 要分配的角色名称列表
     */
    private fun assignUserRoles(realm: String, userId: String, realmRoles: List<String>) = realmRoles.forEach {
        keycloak.realm(realm).users().get(userId).roles().realmLevel().add(listOf(getRealmRole(realm, it)))
    }

    /**
     * 获取领域角色对象
     *
     * @param realm 目标领域名称
     * @param realmRole 角色名称
     * @return 对应的角色表示对象
     */
    private fun getRealmRole(realm: String, realmRole: String) =
        keycloak.realm(realm).roles().get(realmRole).toRepresentation()

    /**
     * 根据登录名查询用户
     *
     * @param login 用户登录名
     * @return 匹配的用户对象，未找到时返回null
     */
    private fun getUserByIdentifier(login: String) = runCatching {
        keycloak.realm(configuration.clientRealm).users().search(login, true).firstOrNull()
    }.getOrNull()

    /**
     * 创建Keycloak领域
     *
     * @param realm 需要创建的领域对象
     * 逻辑说明：
     * - 检查领域是否已存在
     * - 不存在时通过mapper创建领域表示对象
     * - 记录创建日志
     */
    private fun createRealm(realm: KeycloakRealm) =
        keycloak.realms().run { takeIf { realm.notPresentRealm(findAll()) } }
            ?.create(mapper.createRealmRepresentation(realm))
            ?.also { log.info("Realm [${realm.realm}] was created") } ?: Unit

    /**
     * 为领域添加客户端作用域
     *
     * @param realm 目标领域对象
     * 实现逻辑：
     * - 获取领域需要新增的作用域列表
     * - 通过mapper创建作用域表示对象
     * - 批量创建并记录日志
     */
    private fun createRealmScopes(realm: KeycloakRealm) = keycloak.realm(realm.realm).clientScopes().run {
        realm.getScopesToAdd(findAll()).forEach {
            create(mapper.createClientScopeRepresentation(it))
            log.info("Client scopes [$it] added to realm [${realm.realm}]")
        }
    }

    /**
     * 创建领域级角色
     *
     * @param realm 目标领域对象
     * 实现特点：
     * - 仅创建当前领域不存在的角色
     * - 通过mapper转换角色表示对象
     * - 记录每个角色的创建日志
     */
    private fun createRealmRoles(realm: KeycloakRealm) = keycloak.realms().realm(realm.realm).roles().run {
        realm.getRolesToAdd(list()).forEach {
            create(mapper.createRoleRepresentation(it))
            log.info("Realm role [$it] added to ${realm.realm}")
        }
    }

    /**
     * 创建客户端应用
     *
     * @param realm 目标领域名称
     * 核心逻辑：
     * - 根据配置生成客户端对象
     * - 检查客户端是否已存在
     * - 创建客户端后为其添加关联的作用域
     */
    private fun createClient(realm: String) = keycloak.realm(realm).clients().run outer@{
        KeycloakClient(configuration, getRedirectUrls()).takeIf { client ->
            client.notPresent(findAll())
        }?.let { client ->
            create(mapper.createClientRepresentation(client)).run {
                log.info("Client [${client.clientId}] created in [$realm]")
                addClientScopes(realm, client, this@outer, this@run)
            }
        } ?: Unit
    }

    /**
     * 为客户端添加作用域配置
     *
     * @param realm 领域名称
     * @param client 客户端配置对象
     * @param clientsResource 客户端资源操作接口
     * @param response 客户端创建响应对象
     * 实现说明：
     * - 通过响应获取新建客户端的ID
     * - 遍历客户端配置的作用域列表
     * - 为每个作用域建立客户端关联
     */
    private fun addClientScopes(
        realm: String,
        client: KeycloakClient,
        clientsResource: ClientsResource,
        response: Response
    ) = client.scopes.mapNotNull { scope -> getClientScopeId(realm, scope) }
        .forEach { scopeId ->
            clientsResource.get(CreatedResponseUtil.getCreatedId(response)).addOptionalClientScope(scopeId)
            log.info("Client scope [$scopeId] assigned to client [${client.clientId}] in realm [$realm]")
        }

    /**
     * 根据客户端范围名称查找对应的唯一ID
     *
     * @param realm Keycloak中的领域名称，用于指定查找的领域范围
     * @param clientScope 需要查找ID的目标客户端范围名称
     * @return 匹配的客户端范围ID，当不存在匹配项时返回null
     */
    private fun getClientScopeId(realm: String, clientScope: String) = keycloak.realm(realm).clientScopes().findAll()
        .firstOrNull { it.name == clientScope }?.id

    /**
     * 构建重定向URL列表
     *
     * @return 包含以下内容的列表：
     * - 当前主机的IP地址+端口
     * - 本地环回地址（获取失败时的回退方案）
     * - 通配符地址
     */
    private fun getRedirectUrls(): List<String> = buildList {
        runCatching {
            add("http://${InetAddress.getLocalHost().hostAddress}:$port")
        }.onFailure {
            log.error("Could not retrieve host! Fallback to http://localhost:8088 setup!")
        }
        addAll(listOf("http://localhost:8088", "*"))
    }

    companion object {
        const val USER_NOT_FOUND_IN_KEYCLOAK = "User[login: {}] was not found in keycloak."
    }
}